﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Text;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;

/// 
/// ConsoleLib - ANSI Console User Control for Windows Forms Development
/// Written by Scott Hutter
/// scott.hutter@gmail.com
/// Released under the GPL
/// 2012
/// 

namespace ConsoleLib
{   
    // A delegate type for hooking up change notifications.
    public delegate void AnsiCursorLocationRequestEventHandler(object sender, EventArgs e);
    public delegate void AnsiQueryDeviceStatusEventRequestHandler(object sender, EventArgs e);
    public delegate void TextChangedEventHandler(object sender, EventArgs e);
    
    public partial class ANSIConsole : UserControl
    {

        public event AnsiCursorLocationRequestEventHandler AnsiCursorLocationRequest;
        public event AnsiQueryDeviceStatusEventRequestHandler AnsiQueryDeviceStatusRequest;
        public event TextChangedEventHandler TextChanged;

        #region Private Members

        private PrivateFontCollection myFonts = new PrivateFontCollection();

        private const int WIDTH = 727;
        private const int HEIGHT = 410;

        private const int MAXCOLS = 80;
        private const int MAXROWS = 25;

        private char prevChar;
        private bool ansiCmd = false;
        private string ansiParam = "";
        private int ansiSavedCursorRow = 0;
        private int ansiSavedCursorCol = 0;
        private int ansiColorIntensity = 0;
        private string ansiForeColor = "0";
        private string ansiBackColor = "0";
        private int[,] ansiColors = { {000,000,000}, {170,000,000}, {000,170,000}, {170,085,000}, {000,000,170}, {170,000,170}, {000,170,170}, {170,170,170},
                                      {085,085,085}, {255,085,085}, {085,255,085}, {255,255,085}, {085,085,255}, {255,085,255}, {085,255,255}, {255,255,255} };

        private Bitmap bitMap = new Bitmap(WIDTH, HEIGHT);

        private char[] sbuffer = new char[2000];
        private int cursorPtr = 0;
        private int fontSize = 6;
        private int lineSpacing = 4; //5
        private int columnSpacing = -3; //-3;

        private bool cursorMode = true;
        private Color backgroundColor = Color.Black;
        private Color foregroundColor = Color.White;
        private bool localEcho = false;
        private bool readLineMode = false;

        private Brush foreBrush = null;
        private Brush backBrush = null;
        private Font font = null;

        private string readLineBuffer = "";
        private char lastChar = '\0';

        #endregion

        #region Properties

        public Color BackgroundColor
        {
            get { return backgroundColor; }
            set { backBrush = new SolidBrush(value); }
        }

        public Color ForegroundColor
        {
            get { return foregroundColor; }
            set { foreBrush = new SolidBrush(value); }
        }

        public int Row
        {
            get { return (cursorPtr/MAXCOLS) + 1; }
        }

        public int Col
        {
            get { return (cursorPtr % MAXCOLS)+1; }
        }

        public int CurrentBufferLocation
        {
            get { return cursorPtr; }
        }

        public bool CursorMode
        {
            get { return cursorMode; }
            set { cursorMode = value; }
        }

        public bool LocalEcho
        {
            get { return localEcho; }
            set { localEcho = value; }
        }

        private Point CursorToPoint
        {
            get
            {
                Point p = new Point();
                p.Y = (this.Row - 1) * (fontSize * 2 + lineSpacing);
                p.X = (this.Col - 1) * (fontSize * 2 + columnSpacing);
                return p;
            }
        }


        #endregion


        public ANSIConsole()
        {
            InitializeComponent();

            this.CausesValidation = true;            
            this.Width = WIDTH;
            this.Height = HEIGHT;
            foreBrush = new SolidBrush(this.ForegroundColor);
            backBrush = new SolidBrush(this.BackgroundColor);
            
            LoadFont();
        }

        private void ANSIConsole_Load(object sender, EventArgs e)
        {
            if(cursorMode)
                FillCurrentCell(foreBrush);
        }

        private void LoadFont()
        {
            string resource = "ConsoleLib.CP437.TTF";
            string[] names = Assembly.GetExecutingAssembly().GetManifestResourceNames();
            Stream fontStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resource);
            System.IntPtr data = Marshal.AllocCoTaskMem((int)fontStream.Length);
            byte[] fontdata = new byte[fontStream.Length];
            fontStream.Read(fontdata, 0, (int)fontStream.Length);
            Marshal.Copy(fontdata, 0, data, (int)fontStream.Length);
            myFonts.AddMemoryFont(data, (int)fontStream.Length);
            fontStream.Close();
            Marshal.FreeCoTaskMem(data);
            this.font = new Font(myFonts.Families[0], fontSize);
        }

        public void Clear()
        {
            using (Graphics g = Graphics.FromImage(bitMap))
            {
                g.Clear(backgroundColor);
                this.BackgroundImage = bitMap;
                this.Invalidate();
            }

            Goto(1, 1);
        }

        public void CharAt(int row, int col, char c)
        {
            int i = (int)c;

            switch (i)
            {
                case 13:

                    if (this.Row == MAXROWS)
                    {
                        FillCurrentCell(backBrush);
                        ScrollDown();
                        Goto(this.Row, 1);
                    }
                    else
                    {
                        Goto(this.Row + 1, 1);
                    }
                    break;

                case 8:

                    if (this.Col == 1)
                    {
                        if (this.Row > 1)
                        {
                            Goto(this.Row - 1, MAXCOLS);
                        }
                    }
                    else
                    {
                        Goto(this.Row, this.Col - 1);
                    }
                    break;

                default:
                    using (Graphics g = Graphics.FromImage(bitMap))
                    {
                        FillCurrentCell(backBrush);

                        Point p = this.CursorToPoint;
                        p.X = p.X - 1; // needed
  
                        g.DrawString(c.ToString(), font, foreBrush, p);
                        
                        if (this.Col == MAXCOLS && this.Row == MAXROWS)
                        {
                            ScrollDown();
                            Goto(MAXROWS, 1);
                        }
                        else
                        {
                            cursorPtr++;
                            if(cursorMode)
                                FillCurrentCell(foreBrush);
                        }
                    }
                    this.TextChanged(this, null);                    
                    break;
            }

            this.Invalidate();
        }

        public void Goto(int row, int col)
        {
            if (row < 1 || row > MAXROWS || col < 1 || col > MAXCOLS)
            {
                throw new Exception("Row must be between 1 and 80, Column must be between 1 and 24");
            }

            if (cursorMode)
                FillCurrentCell(backBrush);

            cursorPtr = (row - 1) * MAXCOLS + (col - 1);

            if (cursorMode)
                FillCurrentCell(foreBrush);

            this.TextChanged(this, null);
        }

        public void Write(string text)
        {
            byte[] bytes = Encoding.GetEncoding(1252).GetBytes(text);

            string ansiCommands = "HfABCDsuJjKmhn";

            for (int x = 0; x < bytes.Length; x++)
            {
                char c = (char)bytes[x];
                
                // Primitive ANSI command sequence interpreter
                if (c == '[' && prevChar == 27)
                {
                    ansiCmd = true;
                }
                else
                {
                    if (ansiCmd)
                    {
                        if (ansiCommands.IndexOf(c) == -1)
                        {
                            // if this is not an ansi command, then it should be a parameter
                            ansiParam += c;
                            prevChar = c;
                        }
                        else
                        {
                            // this is an ansi command.  call interpreter with the command and parameter
                            System.Diagnostics.Debug.Write(ansiParam);
                            System.Diagnostics.Debug.WriteLine(ansiCommands[ansiCommands.IndexOf(c)]);
                            
                            interpretANSI(ansiCommands[ansiCommands.IndexOf(c)], ansiParam);
                            ansiCmd = false;
                            ansiParam = "";
                            prevChar = c;
                        }

                    }
                    else
                    {
                        // if its printable
                        if ((c > 31 && c < 127) || (c > 127 && c < 255) || c == 13 || c == 8)
                        {
                            sbuffer[cursorPtr] = c;
                            this.CharAt(this.Row, this.Col, sbuffer[cursorPtr]);
                        }
                        prevChar = c;
                    }
                }

            }
        }

        public void WriteLine(string text)
        {
            this.Write(text+"\r");
        }

        public string ReadLine()
        {
            bool currentEcho = this.LocalEcho;
            lastChar = '\0'; 
            readLineBuffer = "";

            this.LocalEcho = true;
            this.Focus();

            readLineMode = true;

            while (true)
            {
                Application.DoEvents();

                if (lastChar == 13)
                    break;
            }

            readLineMode = false;

            string r = readLineBuffer.Replace("\r", "");
            readLineBuffer = "";
            lastChar = '\0';

            this.LocalEcho = currentEcho;

            return r;
        }

        public void ReadFile(string path)
        {
            string text = System.IO.File.ReadAllText(path, Encoding.GetEncoding(1252));

            this.CursorMode = false;

            this.Write(text);

            this.CursorMode = true;
        }

        private void FillCurrentCell(Brush brush)
        {
            Point p = this.CursorToPoint;

            using (Graphics g = Graphics.FromImage(bitMap))
            {
                //g.DrawRectangle(new Pen(brush), p.X, p.Y, fontSize * 2 + columnSpacing, fontSize * 2 + lineSpacing);
                g.FillRectangle(brush, p.X, p.Y, fontSize * 2, fontSize * 2 + lineSpacing);

                this.BackgroundImage = bitMap;
                this.Invalidate();
            }
        }

        private void ScrollDown()
        {
            Bitmap tempBitmap = new Bitmap(this.Width, this.Height);
            Rectangle r1 = new Rectangle(0, fontSize * 2 + lineSpacing, this.Width, this.Height);

            using (Graphics g = Graphics.FromImage(tempBitmap))
            {
                g.DrawImage(bitMap, 0, 0, r1, GraphicsUnit.Pixel);
            }

            bitMap = tempBitmap;
            this.BackgroundImage = bitMap;
            this.Invalidate();

        }

        private void ANSIConsole_KeyPress(object sender, KeyPressEventArgs e)
        {
            char enteredKey = e.KeyChar;

            if (readLineMode)
            {
                lastChar = enteredKey;

                if (enteredKey == 8 && readLineBuffer.Length > 0)
                    readLineBuffer = readLineBuffer.Substring(0, readLineBuffer.Length - 1);
                else
                    readLineBuffer += enteredKey;
            }

            if (localEcho)
            {
                this.Write(enteredKey.ToString());
            }
        }

        private void interpretANSI(char cmd, string param)
        {
            int newRow = 0;
            int newCol = 0;

            try
            {
                switch (cmd)
                {
                    case 'A':
                        {
                            int up = 1;
                            if (param != "")
                                up = Convert.ToInt16(param);

                            if (this.Row - up >= 1)
                                Goto(this.Row - up, this.Col);

                            break;
                        }
                    case 'B':
                        {
                            int down = 1;
                            if (param != "")
                                down = Convert.ToInt16(param);

                            if (this.Row + down <= MAXROWS)
                                Goto(this.Row + down, this.Col);

                            break;
                        }
                    case 'C':
                        {
                            int right = 1;
                            if (param != "")
                                right = Convert.ToInt16(param);

                            if (this.Col + right <= MAXCOLS)
                                Goto(this.Row, this.Col + right);

                            break;
                        }
                    case 'D':
                        {
                            int left = 1;
                            if (param != "")
                                left = Convert.ToInt16(param);

                            if (this.Col - left >= 1)
                                Goto(this.Row, this.Col - left);

                            break;
                        }
                    case 'H':
                    case 'f':
                        {
                            string[] parms = param.Split(';');

                            if (parms[0] == "")
                            {
                                this.Goto(1, 1);
                                break;
                            }

                            if (parms.Length == 1)
                            {
                                newRow = Convert.ToInt16(parms[0]);
                                this.Goto(newRow, 1);
                                break;
                            }

                            if (parms.Length == 2)
                            {
                                newRow = Convert.ToInt16(parms[0]);
                                newCol = Convert.ToInt16(parms[1]);
                                this.Goto(newRow, newCol);
                                break;
                            }
                            break;
                        }
                    case 'h':
                        {
                            //Set mode
                            break;
                        }
                    case 'J':
                    case 'j':
                        this.Clear();
                        this.ForegroundColor = System.Drawing.Color.FromArgb(170, 170, 170);
                        this.BackgroundColor = System.Drawing.Color.FromArgb(0, 0, 0);
                        ansiColorIntensity = 0;
                        break;
                    case 'm':
                        {
                            string[] parms = param.Split(';');

                            for (int x = 0; x < parms.Length; x++)
                            {
                                switch (parms[x])
                                {
                                    case "0":
                                        this.BackgroundColor = System.Drawing.Color.FromArgb(0, 0, 0);
                                        this.ForegroundColor = System.Drawing.Color.FromArgb(170, 170, 170);
                                        ansiColorIntensity = 0;
                                        break;
                                    case "1":
                                        ansiColorIntensity = 1;
                                        break;
                                    case "7":
                                        Color fore = this.ForegroundColor;
                                        Color back = this.BackgroundColor;
                                        this.ForegroundColor = back;
                                        this.BackgroundColor = fore;
                                        break;
                                    case "30":
                                        if (ansiColorIntensity == 1)
                                            this.ForegroundColor = System.Drawing.Color.FromArgb(85, 85, 85);
                                        else
                                            this.ForegroundColor = System.Drawing.Color.FromArgb(0, 0, 0);
                                        ansiParam = "";
                                        break;
                                    case "31":
                                        if (ansiColorIntensity == 1)
                                            this.ForegroundColor = System.Drawing.Color.FromArgb(255, 85, 85);
                                        else
                                            this.ForegroundColor = System.Drawing.Color.FromArgb(170, 0, 0);
                                        ansiParam = "";
                                        break;
                                    case "32":
                                        if (ansiColorIntensity == 1)
                                            this.ForegroundColor = System.Drawing.Color.FromArgb(85, 255, 85);
                                        else
                                            this.ForegroundColor = System.Drawing.Color.FromArgb(0, 170, 0);
                                        ansiParam = "";
                                        break;
                                    case "33":
                                        if (ansiColorIntensity == 1)
                                            this.ForegroundColor = System.Drawing.Color.FromArgb(255, 255, 85);
                                        else
                                            this.ForegroundColor = System.Drawing.Color.FromArgb(170, 85, 0);
                                        ansiParam = "";
                                        break;
                                    case "34":
                                        if (ansiColorIntensity == 1)
                                            this.ForegroundColor = System.Drawing.Color.FromArgb(85, 85, 255);
                                        else
                                            this.ForegroundColor = System.Drawing.Color.FromArgb(0, 0, 170);
                                        ansiParam = "";
                                        break;
                                    case "35":
                                        if (ansiColorIntensity == 1)
                                            this.ForegroundColor = System.Drawing.Color.FromArgb(255, 85, 255);
                                        else
                                            this.ForegroundColor = System.Drawing.Color.FromArgb(170, 0, 170);
                                        ansiParam = "";
                                        break;
                                    case "36":
                                        if (ansiColorIntensity == 1)
                                            this.ForegroundColor = System.Drawing.Color.FromArgb(85, 255, 255);
                                        else
                                            this.ForegroundColor = System.Drawing.Color.FromArgb(0, 170, 170);
                                        ansiParam = "";
                                        break;
                                    case "37":
                                        if (ansiColorIntensity == 1)
                                            this.ForegroundColor = System.Drawing.Color.FromArgb(255, 255, 255);
                                        else
                                            this.ForegroundColor = System.Drawing.Color.FromArgb(170, 170, 170);
                                        ansiParam = "";
                                        break;

                                    case "40":
                                        //if (ansiColorIntensity == 1)
                                        //    this.BackgroundColor = System.Drawing.Color.FromArgb(85, 85, 85);
                                        //else
                                            this.BackgroundColor = System.Drawing.Color.FromArgb(0, 0, 0);
                                        ansiParam = "";
                                        break;
                                    case "41":
                                        //if (ansiColorIntensity == 1)
                                        //    this.BackgroundColor = System.Drawing.Color.FromArgb(255, 85, 85);
                                        //else
                                            this.BackgroundColor = System.Drawing.Color.FromArgb(170, 0, 0);
                                        ansiParam = "";
                                        break;
                                    case "42":
                                        //if (ansiColorIntensity == 1)
                                        //    this.BackgroundColor = System.Drawing.Color.FromArgb(85, 255, 85);
                                        //else
                                            this.BackgroundColor = System.Drawing.Color.FromArgb(0, 170, 0);
                                        ansiParam = "";
                                        break;
                                    case "43":
                                        //if (ansiColorIntensity == 1)
                                        //    this.BackgroundColor = System.Drawing.Color.FromArgb(170, 85, 0);
                                        //else
                                            this.BackgroundColor = System.Drawing.Color.FromArgb(170, 85, 0);
                                        ansiParam = "";
                                        break;
                                    case "44":
                                        //if (ansiColorIntensity == 1)
                                        //   this.BackgroundColor = System.Drawing.Color.FromArgb(85, 85, 255);
                                        //else
                                            this.BackgroundColor = System.Drawing.Color.FromArgb(0, 0, 170);
                                        ansiParam = "";
                                        break;
                                    case "45":
                                        //if (ansiColorIntensity == 1)
                                        //    this.BackgroundColor = System.Drawing.Color.FromArgb(255, 85, 255);
                                        //else
                                            this.BackgroundColor = System.Drawing.Color.FromArgb(170, 0, 170);
                                        ansiParam = "";
                                        break;
                                    case "46":
                                        //if (ansiColorIntensity == 1)
                                        //    this.BackgroundColor = System.Drawing.Color.FromArgb(85, 255, 255);
                                        //else
                                            this.BackgroundColor = System.Drawing.Color.FromArgb(0, 170, 170);
                                        ansiParam = "";
                                        break;
                                    case "47":
                                        //if (ansiColorIntensity == 1)
                                        //    this.BackgroundColor = System.Drawing.Color.FromArgb(255, 255, 255);
                                        //else
                                            this.BackgroundColor = System.Drawing.Color.FromArgb(170, 170, 170);
                                        ansiParam = "";
                                        break;

                                    default:
                                        ansiParam = "";
                                        break;
                                }
                            }
                        }
                        break;
                    case 'n':
                        {   
                            if (param == "5")
                            {
                                this.AnsiQueryDeviceStatusRequest(this, new System.EventArgs());
                            }
                            if (param == "6")
                            {
                                
                                this.AnsiCursorLocationRequest(this, new System.EventArgs());
                            }
                            break;
                        }

                    case 's':
                        {
                            this.ansiSavedCursorCol = this.Col;
                            this.ansiSavedCursorRow = this.Row;
                            break;
                        }

                    case 'u':
                        {
                            this.Goto(ansiSavedCursorRow, ansiSavedCursorCol);
                            break;
                        }
                    default:
                        //eat the code
                        break;
                }
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine("Unknown ansi error");
                return;
            }

        }

    }
}
